2 * Copyright (c) 2017-2019 Apple Inc. All rights reserved.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 #include "unittest_common.h"
18 #include "mDNSMacOSX.h"
19 #import <XCTest/XCTest.h>
21 // This query request message was generated from the following command: "dns-sd -lo -timeout -Q cardinal2.apple.com. A"
22 char query_req_msgbuf[33]= {
23 0x00, 0x01, 0x90, 0x00,
24 // DNSServiceFlags.L = (kDNSServiceFlagsReturnIntermediates |kDNSServiceFlagsSuppressUnusable | kDNSServiceFlagsTimeout)
25 0xff, 0xff, 0xff, 0xff,
26 // interfaceIndex = mDNSInterface_LocalOnly
27 0x63, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x6c,
28 0x32, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x00, 0x00, 0x01, 0x00,
32 mDNSlocal mStatus InitEtcHostsRecords(void)
34 mDNS *m = &mDNSStorage;
35 struct sockaddr_storage hostaddr;
38 mDNSPlatformMemZero(&newhosts, sizeof(AuthHash));
40 memset(&hostaddr, 0, sizeof(hostaddr));
41 get_ip("127.0.0.1", &hostaddr);
44 MakeDomainNameFromDNSNameString(&domain, "localhost");
46 mDNSMacOSXCreateEtcHostsEntry_ut(&domain, (struct sockaddr *) &hostaddr, mDNSNULL, mDNSNULL, &newhosts);
48 memset(&hostaddr, 0, sizeof(hostaddr));
49 get_ip("0000:0000:0000:0000:0000:0000:0000:0001", &hostaddr);
51 MakeDomainNameFromDNSNameString(&domain, "localhost");
53 mDNSMacOSXCreateEtcHostsEntry_ut(&domain, (struct sockaddr *) &hostaddr, mDNSNULL, mDNSNULL, &newhosts);
55 memset(&hostaddr, 0, sizeof(hostaddr));
56 get_ip("255.255.255.255", &hostaddr);
58 MakeDomainNameFromDNSNameString(&domain, "broadcasthost");
60 mDNSMacOSXCreateEtcHostsEntry_ut(&domain, (struct sockaddr *) &hostaddr, mDNSNULL, mDNSNULL, &newhosts);
62 memset(&hostaddr, 0, sizeof(hostaddr));
63 get_ip("17.226.40.200", &hostaddr);
65 MakeDomainNameFromDNSNameString(&domain, "cardinal2.apple.com");
67 mDNSMacOSXCreateEtcHostsEntry_ut(&domain, (struct sockaddr *) &hostaddr, mDNSNULL, mDNSNULL, &newhosts);
68 UpdateEtcHosts_ut(&newhosts);
70 m->NextScheduledEvent = mDNS_TimeNow_NoLock(m);
73 return mStatus_NoError;
76 @interface LocalOnlyATimeoutTest : XCTestCase
78 request_state* client_request_message;
79 UDPSocket* local_socket;
80 char domainname_cstr[MAX_ESCAPED_DOMAIN_NAME];
84 @implementation LocalOnlyATimeoutTest
86 // The InitUnitTest() initializes a minimal mDNSResponder environment as
87 // well as allocates memory for a local_socket and client request.
88 // It also sets the domainname_cstr specified in the client's query request.
89 // Note: This unit test does not send packets on the wire and it does not open sockets.
93 mStatus result = init_mdns_storage();
94 XCTAssertEqual(result, mStatus_NoError);
96 // Allocate a client request
97 local_socket = (UDPSocket *)calloc(1, sizeof(*local_socket));
99 // Allocate memory for a request that is used to make client requests.
100 client_request_message = calloc(1, sizeof(request_state));
102 // Init domainname that is used by unit tests
103 strlcpy(domainname_cstr, "cardinal2.apple.com.", sizeof(domainname_cstr));
106 // This function does memory cleanup and no verification.
109 mDNSPlatformMemFree(local_socket);
112 // This unit test starts a local only request for "cardinal2.apple.com.". It first
113 // calls start_client_request to start a query, it then verifies the
114 // req and query data structures are set as expected. Next, the cache is verified to
115 // be empty by AnswerNewLocalOnlyQuestion() and so results in GenerateNegativeResponse()
116 // getting called which sets up a reply with a negative answer in it for the client.
117 // On return from mDNS_Execute, the client's reply structure is verified to be set as
118 // expected. Lastly the timeout is simulated and mDNS_Execute is called. This results
119 // in a call to TimeoutQuestions(). And again, the GenerateNegativeResponse() is called
120 // which returns a negative response to the client. This time the client reply is verified
121 // to be setup with a timeout result.
122 - (void)testStartLocalOnlyClientQueryRequest
124 mDNS *const m = &mDNSStorage;
125 request_state* req = client_request_message;
126 char *msgptr = (char *)query_req_msgbuf;
127 size_t msgsz = sizeof(query_req_msgbuf);
129 mDNSs32 min_size = sizeof(DNSServiceFlags) + sizeof(mDNSu32) + 4;
130 mStatus err = mStatus_NoError;
131 char qname_cstr[MAX_ESCAPED_DOMAIN_NAME];
132 struct reply_state *reply;
135 // Process the unit test's client request
136 start_client_request(req, msgptr, msgsz, query_request, local_socket);
137 XCTAssertEqual(err, mStatus_NoError);
139 // Verify the query initialized and request fields were set as expected
140 XCTAssertEqual(req->hdr.version, VERSION);
141 XCTAssertGreaterThan((mDNSs32)req->data_bytes, min_size);
142 XCTAssertEqual(req->flags, (kDNSServiceFlagsSuppressUnusable | kDNSServiceFlagsReturnIntermediates | kDNSServiceFlagsTimeout));
143 XCTAssertEqual(req->interfaceIndex, kDNSServiceInterfaceIndexLocalOnly);
144 XCTAssertNotEqual(req->terminate, (req_termination_fn)0);
146 q = &req->u.queryrecord.op.q;
147 XCTAssertEqual(q, m->NewLocalOnlyQuestions);
148 XCTAssertNil((__bridge id)m->Questions);
149 XCTAssertNil((__bridge id)m->NewQuestions);
150 XCTAssertEqual(q->SuppressUnusable, 1);
151 XCTAssertEqual(q->ReturnIntermed, 1);
152 XCTAssertEqual(q->Suppressed, mDNSfalse); // Regress <rdar://problem/27571734>
154 ConvertDomainNameToCString(&q->qname, qname_cstr);
155 XCTAssertFalse(strcmp(qname_cstr, domainname_cstr));
156 XCTAssertEqual(q->qnamehash, DomainNameHashValue(&q->qname));
158 XCTAssertEqual(q->InterfaceID, mDNSInterface_LocalOnly);
159 XCTAssertEqual(q->flags, req->flags);
160 XCTAssertEqual(q->qtype, 1);
161 XCTAssertEqual(q->qclass, 1);
162 XCTAssertEqual(q->LongLived, 0);
163 XCTAssertEqual(q->ExpectUnique, mDNSfalse);
164 XCTAssertEqual(q->ForceMCast, 0);
165 XCTAssertEqual(q->TimeoutQuestion, 1);
166 XCTAssertEqual(q->WakeOnResolve, 0);
167 XCTAssertEqual(q->UseBackgroundTraffic, 0);
168 XCTAssertEqual(q->ValidationRequired, 0);
169 XCTAssertEqual(q->ValidatingResponse, 0);
170 XCTAssertEqual(q->ProxyQuestion, 0);
171 XCTAssertNotEqual((void*)q->QuestionCallback, (void*)mDNSNULL);
172 XCTAssertNil((__bridge id)q->DNSSECAuthInfo);
173 XCTAssertNil((__bridge id)(void*)q->DAIFreeCallback);
174 XCTAssertNotEqual(q->StopTime, 0);
175 XCTAssertEqual(q->AppendSearchDomains, 0);
176 XCTAssertNil((__bridge id)q->DuplicateOf);
178 // At this point the the cache is empty. Calling mDNS_Execute will answer the local-only
179 // question with a negative response.
180 m->NextScheduledEvent = mDNS_TimeNow_NoLock(m);
181 mDNS_Execute(m); // Regress <rdar://problem/28721294>
183 // Verify reply is a negative response and error code is set to kDNSServiceErr_NoSuchRecord error.
184 reply = req->replies;
185 XCTAssertNotEqual(reply, (reply_state*)mDNSNULL);
187 XCTAssertNil((__bridge id)m->NewLocalOnlyQuestions);
188 XCTAssertEqual(q->LOAddressAnswers, 0);
190 len = get_reply_len(qname_cstr, 0);
192 XCTAssertNil((__bridge id)reply->next);
193 XCTAssertEqual(reply->totallen, reply->mhdr->datalen + sizeof(ipc_msg_hdr));
194 XCTAssertEqual(reply->mhdr->version, VERSION);
195 XCTAssertEqual(reply->mhdr->datalen, len);
196 XCTAssertEqual(reply->mhdr->ipc_flags, 0);
197 XCTAssertEqual(reply->mhdr->op, query_reply_op);
199 XCTAssertTrue((reply->rhdr->flags & htonl(kDNSServiceFlagsAdd)));
200 XCTAssertEqual(reply->rhdr->ifi, kDNSServiceInterfaceIndexLocalOnly); // Regress <rdar://problem/27340874>
201 XCTAssertEqual(reply->rhdr->error,
202 (DNSServiceErrorType)htonl(kDNSServiceErr_NoSuchRecord)); // Regress <rdar://problem/24827555>
204 // Simulate what udsserver_idle normally does for clean up
205 freeL("StartLocalOnlyClientQueryRequest:reply", reply);
208 // Simulate the query time out of the local-only question.
209 // The expected behavior is a negative answer with time out error
210 m->NextScheduledEvent = mDNS_TimeNow_NoLock(m);
211 q->StopTime = mDNS_TimeNow_NoLock(m);
212 m->NextScheduledStopTime -= mDNSPlatformOneSecond*5;
215 // Verify the reply is a negative response with timeout error.
216 reply = req->replies;
217 XCTAssertNotEqual(reply, (reply_state*)mDNSNULL);
218 XCTAssertNil((__bridge id)m->NewLocalOnlyQuestions);
219 XCTAssertEqual(q->LOAddressAnswers, 0);
221 len = get_reply_len(qname_cstr, 0);
223 XCTAssertNil((__bridge id)reply->next);
224 XCTAssertEqual(reply->totallen, len + sizeof(ipc_msg_hdr));
225 XCTAssertEqual(reply->mhdr->version, VERSION);
226 XCTAssertEqual(reply->mhdr->datalen, len);
227 XCTAssertEqual(reply->mhdr->ipc_flags, 0);
228 XCTAssertEqual(reply->mhdr->op, query_reply_op);
229 XCTAssertTrue((reply->rhdr->flags & htonl(kDNSServiceFlagsAdd)));
230 XCTAssertEqual(reply->rhdr->ifi, kDNSServiceInterfaceIndexLocalOnly); // Regress <rdar://problem/27340874>
231 XCTAssertEqual(reply->rhdr->error,
232 (DNSServiceErrorType)htonl(kDNSServiceErr_Timeout)); // Regress <rdar://problem/27562965>
234 // Free request and reallocate to use when query is restarted
236 client_request_message = calloc(1, sizeof(request_state));
239 // This unit test populates the cache with four /etc/hosts records and then
240 // verifies there are four entries in the cache.
241 - (void)testPopulateCacheWithClientLOResponseRecords
243 mDNS *const m = &mDNSStorage;
245 // Verify cache is empty
246 int count = LogEtcHosts_ut(m);
247 XCTAssertEqual(count, 0);
249 // Populate /etc/hosts
250 mStatus result = InitEtcHostsRecords();
251 XCTAssertEqual(result, mStatus_NoError);
253 // mDNS_Execute is called to populate the /etc/hosts cache.
254 m->NextScheduledEvent = mDNS_TimeNow_NoLock(m);
257 count = LogEtcHosts_ut(m);
258 XCTAssertEqual(count, 4);
260 [self _testRestartLocalOnlyClientQueryRequest]; // Continuation of this test
263 // This unit test starts a local only request for "cardinal2.apple.com.". It first
264 // calls start_client_request to start a query, it then verifies the
265 // req and query data structures are set as expected. Next, the cache is verified to
266 // contain the answer by AnswerNewLocalOnlyQuestion() and so results in setting up an
267 // answer reply to the client. On return from mDNS_Execute, the client's reply structure
268 // is verified to be set as expected. Lastly the timeout is simulated and mDNS_Execute is
269 // called. This results in a call to TimeoutQuestions(). And this time, the
270 // GenerateNegativeResponse() is called which returns a negative response to the client
271 // which specifies the timeout occurred. Again, the answer reply is verified to
272 // to specify a timeout.
273 - (void)_testRestartLocalOnlyClientQueryRequest
275 mDNS *const m = &mDNSStorage;
276 request_state* req = client_request_message;
277 char *msgptr = (char *)query_req_msgbuf;
278 size_t msgsz = sizeof(query_req_msgbuf); DNSQuestion *q;
279 mDNSs32 min_size = sizeof(DNSServiceFlags) + sizeof(mDNSu32) + 4;
280 mStatus err = mStatus_NoError;
281 char qname_cstr[MAX_ESCAPED_DOMAIN_NAME];
282 struct reply_state *reply;
285 // Process the unit test's client request
286 start_client_request(req, msgptr, msgsz, query_request, local_socket);
287 XCTAssertEqual(err, mStatus_NoError);
289 XCTAssertEqual(req->hdr.version, VERSION);
290 XCTAssertGreaterThan((mDNSs32)req->data_bytes, min_size);
291 XCTAssertEqual(req->flags, (kDNSServiceFlagsSuppressUnusable | kDNSServiceFlagsReturnIntermediates | kDNSServiceFlagsTimeout));
292 XCTAssertEqual(req->interfaceIndex, kDNSServiceInterfaceIndexLocalOnly);
293 XCTAssertNotEqual(req->terminate, (req_termination_fn)0);
294 XCTAssertNil((__bridge id)m->Questions);
296 q = &req->u.queryrecord.op.q;
297 XCTAssertEqual(q, m->NewLocalOnlyQuestions);
298 XCTAssertEqual(q->SuppressUnusable, 1);
299 XCTAssertEqual(q->ReturnIntermed, 1);
300 XCTAssertEqual(q->Suppressed, mDNSfalse); // Regress <rdar://problem/27571734>
301 XCTAssertEqual(q->qnamehash, DomainNameHashValue(&q->qname));
302 XCTAssertEqual(q->InterfaceID, mDNSInterface_LocalOnly);
303 XCTAssertEqual(q->flags, req->flags);
304 XCTAssertEqual(q->qtype, 1);
305 XCTAssertEqual(q->qclass, 1);
306 XCTAssertEqual(q->LongLived, 0);
307 XCTAssertEqual(q->ExpectUnique, mDNSfalse);
308 XCTAssertEqual(q->ForceMCast, 0);
309 XCTAssertEqual(q->TimeoutQuestion, 1);
310 XCTAssertEqual(q->WakeOnResolve, 0);
311 XCTAssertEqual(q->UseBackgroundTraffic, 0);
312 XCTAssertEqual(q->ValidationRequired, 0);
313 XCTAssertEqual(q->ValidatingResponse, 0);
314 XCTAssertEqual(q->ProxyQuestion, 0);
315 XCTAssertNotEqual((void*)q->QuestionCallback, (void*)mDNSNULL);
316 XCTAssertNil((__bridge id)q->DNSSECAuthInfo);
317 XCTAssertNil((__bridge id)(void*)q->DAIFreeCallback);
318 XCTAssertNotEqual(q->StopTime, 0);
319 XCTAssertEqual(q->AppendSearchDomains, 0);
320 XCTAssertNil((__bridge id)q->DuplicateOf);
321 ConvertDomainNameToCString(&q->qname, qname_cstr);
322 XCTAssertFalse(strcmp(qname_cstr, domainname_cstr));
324 // Answer local-only question with found cache entry
325 m->NextScheduledEvent = mDNS_TimeNow_NoLock(m);
326 mDNS_Execute(m); // Regress <rdar://problem/28721294>
327 XCTAssertNil((__bridge id)m->NewLocalOnlyQuestions);
328 XCTAssertEqual(req->u.queryrecord.op.answered, 1);
329 XCTAssertEqual(q->LOAddressAnswers, 1);
330 XCTAssertEqual(q, m->LocalOnlyQuestions);
332 reply = req->replies;
333 len = get_reply_len(qname_cstr, 4);
335 XCTAssertNil((__bridge id)reply->next);
336 XCTAssertEqual(reply->totallen, len + sizeof(ipc_msg_hdr));
337 XCTAssertEqual(reply->mhdr->version, VERSION);
338 XCTAssertEqual(reply->mhdr->datalen, len);
339 XCTAssertEqual(reply->mhdr->ipc_flags, 0);
340 XCTAssertEqual(reply->mhdr->op, query_reply_op);
341 XCTAssertTrue((reply->rhdr->flags & htonl(kDNSServiceFlagsAdd)));
342 XCTAssertEqual(reply->rhdr->ifi, kDNSServiceInterfaceIndexLocalOnly); // Regress <rdar://problem/27340874>
343 XCTAssertEqual(reply->rhdr->error, kDNSServiceErr_NoError);
345 // Simulate the query time out of the local-only question.
346 // The expected behavior is a negative answer with time out error
347 m->NextScheduledEvent = mDNS_TimeNow_NoLock(m);
348 q->StopTime = mDNS_TimeNow_NoLock(m);
349 m->NextScheduledStopTime -= mDNSPlatformOneSecond*5;
352 reply = req->replies->next;
353 XCTAssertNotEqual(reply, (reply_state*)mDNSNULL);
354 XCTAssertNil((__bridge id)reply->next);
355 XCTAssertNil((__bridge id)m->NewLocalOnlyQuestions);
356 XCTAssertEqual(q->LOAddressAnswers, 0);
357 len = get_reply_len(qname_cstr, 0);
359 XCTAssertNil((__bridge id)reply->next);
360 XCTAssertEqual(reply->totallen, len + + sizeof(ipc_msg_hdr));
361 XCTAssertEqual(reply->mhdr->version, VERSION);
362 XCTAssertEqual(reply->mhdr->datalen, len);
363 XCTAssertEqual(reply->mhdr->ipc_flags, 0);
364 XCTAssertEqual(reply->mhdr->op, query_reply_op);
365 XCTAssertTrue((reply->rhdr->flags & htonl(kDNSServiceFlagsAdd)));
366 XCTAssertEqual(reply->rhdr->ifi, kDNSServiceInterfaceIndexLocalOnly); // Regress <rdar://problem/27340874>
367 XCTAssertEqual(reply->rhdr->error,
368 (DNSServiceErrorType)htonl(kDNSServiceErr_Timeout)); // Regress <rdar://problem/27562965>